#include "../include/gui/ScrollArea.hpp"
#include "../include/gui/GuiManager.hpp"
#include "../include/gui/GridLayout.hpp"
#include "../include/gui/Slider.hpp"
#include <sstream>

namespace gui
{
	ScrollArea::ScrollArea()
	{
		m_type = SCROLL_AREA;
		m_verticalSlider   = new Slider;
		m_horizontalSlider = new Slider;
		m_gridLayout	   = new GridLayout;
		m_view			   = new ScrollAreaView(m_rect);

		m_verticalSlider->SetName("vertical_slider");
		m_horizontalSlider->SetName("horizontal_slider");
		m_gridLayout->SetName("grid_layout");
		m_gridLayout->SetMovable(false);
		m_view->SetName("view_area");

		m_horizontalSlider->SetMovable(false);
		m_verticalSlider->SetMovable(false);
		m_gridLayout->SetMovable(false);
		m_view->SetMovable(false);

		m_horizontalSlider->Hide();
		m_verticalSlider->Hide();

		//buttons particular size hint
		m_sizeHint.x = 100;
		m_sizeHint.y = 100;

		//buttons particular size policy
		m_horizontalPolicy	= MinimumExpand;
		m_verticalPolicy	= MinimumExpand;	

		m_verticalSlider->AllowSave(false);
		m_horizontalSlider->AllowSave(false);
		m_view->AllowSave(false);
		m_gridLayout->AllowSave(false);

		m_verticalSlider->SetSliderType(Slider::Vertical);

		m_mediator.Connect(m_verticalSlider,"default",gui::events::OnValueChanged,false);
		m_mediator.Connect(m_horizontalSlider,"default",gui::events::OnValueChanged,false);

		m_gridLayout->AddWidgetToGrid(m_view,0,0);
		m_gridLayout->AddWidgetToGrid(m_verticalSlider,0,1);
		m_gridLayout->AddWidgetToGrid(m_horizontalSlider,1,0);

		m_gridLayout->SetHorizontalPolicy(gui::Widget::MinimumExpand);
		m_gridLayout->SetVerticalPolicy(gui::Widget::MinimumExpand);
		
		Widget::AddWidget(m_gridLayout);
	}
	ScrollArea::ScrollArea(Rect& rect)
	{
		SetScrollArea(rect);
	}

	const Rect& ScrollArea::GetScrollArea() const
	{
		return m_view->GetScrollArea();
	}

	void ScrollArea::SetScrollArea(Rect& rect)
	{
		//the scroll area is larger than my width(will need a slider)
		if(rect.w > m_rect.w) {
			m_horizontalSlider->Show();
		}
		if(rect.h > m_rect.h) {
			m_verticalSlider->Show();
		}
		rect.x = 0;
		rect.y = 0;

		m_view->SetScrollArea(rect);
	}

	void ScrollArea::Update(float diff)
	{
		Widget::Update(diff);

		Event* e = NULL;

		while(e = m_mediator.GetEvent()) {
			switch(e->GetType()) {
			case gui::events::OnValueChanged:
			{
				gui::OnValueChanged* we = (gui::OnValueChanged*)e;
				if(we->GetWidget() == m_verticalSlider) {
					m_view->HandleVerticalScroll(we);
				} else if(we->GetWidget() == m_horizontalSlider) {
					m_view->HandleHorizontalScroll(we);
				} else {
					error_log("Received OnValueChanged event from unsupported widget: %s!", we->GetWidget()->GetName().c_str());
				}
			} break;
			default:
				error_log("Received unsupported event type: %u", e->GetType());
				break;
			}
		}
	}

	void ScrollAreaView::HandleHorizontalScroll(gui::OnValueChanged* v)
	{
		if(!v) return;

		float percent = (float)v->GetValue()/100;
		
		int32 width = m_scrollArea.w - m_rect.w;

		if(width < 0) {
			error_log("width is less than 0! This shouldn't have ever happened!");
			return;
		}
		uint32 displacement = uint32(percent* width);

		m_scrollArea.x = 0 + displacement;
	}

	void ScrollAreaView::HandleVerticalScroll(gui::OnValueChanged* v)
	{
		if(!v) return;
		
		float percent = (float)v->GetValue()/100;
		
		int32 height = m_scrollArea.h - m_rect.h;

		if(height < 0) {
			error_log("height is less than 0! This shouldn't have ever happened!");
			return;
		}
		uint32 displacement = uint32(percent* height);

		m_scrollArea.y = 0 + displacement;
	}


	ScrollAreaView::ScrollAreaView(Rect& scroll_area)
	{
		//buttons particular size hint
		m_sizeHint.x = 100;
		m_sizeHint.y = 100;

		//buttons particular size policy
		m_horizontalPolicy	= MaximumExpand;
		m_verticalPolicy	= MaximumExpand;	

		m_movable = false;
		m_strictPos = false;	//don't let child widgets be bound to your rect
		m_scrollArea = scroll_area;
	}

	void ScrollAreaView::SetScrollArea(Rect& scroll_area)
	{
		m_scrollArea = scroll_area;

		//area changed.. rebind all widgets!
		BindAllWidgetsToArea();
	}

	bool ScrollAreaView::AddWidget(Widget* child)
	{
		if(!Widget::AddWidget(child))
			return false;

		BindWidgetToArea(child);

		return true;
	}

	void ScrollAreaView::BindWidgetToArea(Widget* widget)
	{
// 		Rect temp = widget->GetRect();
// 
// 		//limit the child within the scroll area
// 		if(temp.x < m_scrollArea.x)
// 			temp.x = m_scrollArea.x;
// 		else if(temp.GetRight() > m_scrollArea.GetRight())
// 			temp.x = m_scrollArea.GetRight() - temp.w;
// 
// 		if(temp.y < m_scrollArea.y)
// 			temp.y = m_scrollArea.y;
// 		else if(temp.GetBottom() > m_scrollArea.GetBottom())
// 			temp.y = m_scrollArea.GetBottom() - temp.h;
// 
// 		if(temp != widget->GetRect()) {
// 			widget->SetPos(temp.x, temp.y);
// 		}
	}

	void ScrollAreaView::BindAllWidgetsToArea()
	{
		for(WidgetList::iterator it = m_widgets.begin();
			it != m_widgets.end(); it++)
		{
			BindWidgetToArea(it->second);
		}
	}

	const Rect& ScrollAreaView::GetScrollArea() const
	{
		return m_scrollArea;
	}

	void ScrollAreaView::Update(float diff)
	{
		Widget::Update(diff);
	}

	void ScrollArea::ReloadSettings()
	{
		Widget::ReloadSettings();
		
		Rect temp;

		if(m_settings.HasUint32Value("area-width")) {
			 temp.w = m_settings.GetUint32Value("area-width");
		}
		if(m_settings.HasUint32Value("area-height")) {
			temp.h = m_settings.GetUint32Value("area-height");
		}


		if(!temp) 
			return;
		temp.x = m_rect.x;
		temp.y = m_rect.y;

		SetScrollArea(temp);		
	}

	bool ScrollArea::AddWidget(Widget* child)
	{
		//don't add widgets to myself.. I'm just a wrapper!
		if(!m_view->AddWidget(child))
			return false;

		return true;	
	}

	void ScrollArea::InitGraphics()
	{
		//TODO: hardcoded value!
		m_shape.SetColor(sf::Color(0,128,128));
	}

	bool ScrollArea::AcceptsDrop(Drag* drag) const
	{
		return false;
	}

	void ScrollAreaView::BetaDraw() const
	{
		if(!m_visible)
			return;

		//m_clipArea.Update(m_rect);

		const sf::View& old = s_gui->GetWindow().GetView();

		float width  = old.GetHalfSize().x*2;
		float height = old.GetHalfSize().y*2;

		sf::View new_view(sf::FloatRect((float)m_scrollArea.x,(float)m_scrollArea.y,
										(float)m_scrollArea.x+width,(float)m_scrollArea.y+height));

		s_gui->GetWindow().SetView(new_view);

		for(WidgetList::iterator it = m_widgets.begin();
			it != m_widgets.end(); it++) 
		{
			it->second->AlphaDraw();
			it->second->BetaDraw();
		}
		s_gui->GetWindow().SetView(old);
// 		sf::Vector2i pos = GetViewPosition(new_view);
// 		int32 x,y;
// 		x = m_clipRect.x - pos.x;
// 		y = m_rect.y - pos.y;
// 
//  		sf::Shape shape = sf::Shape::Rectangle(0,0, m_clipRect.w, m_clipRect.h,sf::Color(255,0,255,128));
// 		sf::Color color(255,0,255,129);
// 		shape.AddPoint(sf::Vector2f(m_clipRect.x,m_clipRect.y),color);
// 		shape.AddPoint(sf::Vector2f(m_clipRect.x,m_clipRect.y- m_clipRect.h),color);
// 		shape.AddPoint(sf::Vector2f(m_clipRect.x+m_clipRect.w, m_clipRect.y-m_clipRect.h),color);
// 		shape.AddPoint(sf::Vector2f(m_clipRect.x+m_clipRect.w, m_clipRect.y),color);
// 
// 		shape.Move(x, y);
// 		sf::String name;
// 		name.SetPosition(x,y+2);
// 		name.SetSize(12);
// 		std::stringstream s;
// 		s << m_name << std::endl << m_clipRect;
// 		s_gui->GetWindow().Draw(shape);
// 		name.SetText(s.str());
// 		s_gui->GetWindow().Draw(name);


// 
// 		//also draw children if any
// 		for(WidgetList::const_iterator it = m_widgets.begin(); it != m_widgets.end(); it++) {
// 			//don't draw outside parent's rect
// 			Rect rect = it->second->GetRect();
// 			rect.x -= m_scrollArea.x; rect.y -= m_scrollArea.y;
// 			if(IsCollision(rect)) {
// 				s_gui->GetWindow().SetView(new_view);
// 			//StartClipping();
// 				it->second->Draw();
// 			//StopClipping();
// 			} else {
// 				s_gui->GetWindow().SetView(new_view);
// 			}
// 			//StartClipping();
// 				it->second->Draw();
// 			//StopClipping();
// 		}
// 		s_gui->GetWindow().SetView(old);

		//TODO: this is debug code..
		//sf::Shape view = sf::Shape::Rectangle(m_scrollArea.x,m_scrollArea.y,m_scrollArea.x+width, m_scrollArea.y+height,sf::Color(255,255,0,128));
		//s_gui->GetWindow().Draw(view);
	}

	void ScrollAreaView::SetPos(int x, int y, bool forceMove/*= false*/, bool save/*=true*/)
	{
		if(!m_movable && !forceMove) return;

		m_needUpdate = true;

		Rect temp = m_rect;
		m_rect.x = x; 
		m_rect.y = y;

		//error checking to keep widgets in place, to allow outside 
		//of parents rect, modify the drag flags to anywhere?
		if(m_parent && m_dropFlags == Drag::WidgetOnly ) 
		{
			const Rect& prect = m_parent->GetRect();
			const WidgetList& widgets = m_parent->GetWidgetList();
			if(m_parent->IsStrictPos()) {
				//don't allowed then leave the parent's rect
				if(m_rect.x < prect.x)
					m_rect.x = prect.x;
				if(m_rect.y < prect.y)
					m_rect.y = prect.y;

				//don't allowed then leave the parent's rect
				if(m_rect.x+m_rect.w > prect.x+prect.w) {
					m_rect.x = prect.x + prect.w - m_rect.w;
				}
				if(m_rect.y+m_rect.h > prect.y+prect.h) {
					m_rect.y = prect.y + prect.h - m_rect.h;
				}	
			}
		}
				
		//also move child widgets
		for(WidgetList::iterator it = m_widgets.begin(); it != m_widgets.end(); it++) {
			Widget* widget = it->second;
			Rect childRect = widget->GetRect();
			int xpos = m_rect.x - temp.x + childRect.x;
			int ypos = m_rect.y - temp.y + childRect.y ;
			widget->SetPos(xpos,ypos,true); //force child widgets to move!
		}
		if(save) {
			m_settings.SetInt32Value("posx",m_rect.x);
			m_settings.SetInt32Value("posy",m_rect.y);
		}

		if(!m_sprite)
			m_shape.SetPosition(m_rect.GetPos());
		else m_sprite->SetPosition(m_rect.GetPos());

		const sf::View& old = s_gui->GetWindow().GetView();

		float width  = old.GetHalfSize().x*2;
		float height = old.GetHalfSize().y*2;
		
		m_rect.x -= m_scrollArea.x;
		m_rect.y -= m_scrollArea.y;
			UpdateClipArea();
		m_rect.x += m_scrollArea.x;
		m_rect.y += m_scrollArea.y;
	}

	void ScrollAreaView::_HandleEvents()
	{
		if(!m_events.size()) 
			return;

		const sf::View& old = s_gui->GetWindow().GetView();

		float width  = old.GetHalfSize().x*2;
		float height = old.GetHalfSize().y*2;

		sf::View new_view(sf::FloatRect((float)m_scrollArea.x,(float)m_scrollArea.y,
										(float)m_scrollArea.x+width,(float)m_scrollArea.y+height));

		s_gui->GetWindow().SetView(new_view);
			Widget::_HandleEvents();
		s_gui->ChangeView(old);
	}

	void ScrollAreaView::InitGraphics()
	{
		//TODO: hardcoded value
		m_shape.SetColor(sf::Color(0,255,255));
	}

	void ScrollArea::SetPos(int x, int y, bool forceMove/*= false*/, bool save/*= true*/)
	{
		Widget::SetPos(x,y,forceMove, save);


		if(!m_verticalSlider->IsHidden())
			m_verticalSlider->SetValue(m_verticalSlider->GetValue());
		if(!m_horizontalSlider->IsHidden())
			m_horizontalSlider->SetValue(m_horizontalSlider->GetValue());
	}

	bool ScrollAreaView::HandleDragStop( Drag* drag )
	{
		if(!drag) 
			return false;
		
		Widget* target = drag->GetTarget();

		sf::View old = s_gui->GetWindow().GetView();
		float width  = old.GetHalfSize().x*2;
		float height = old.GetHalfSize().y*2;
		
		//apply view transformations to the current drag?
		sf::View view(sf::FloatRect((float)m_scrollArea.x, (float)m_scrollArea.y,
									(float)m_scrollArea.x+(float)width,(float)m_scrollArea.y+(float)height));
		
		//if the mouse is NOT within the view area, don't apply view transformations
		if(!gui::IsCollision(m_rect,Rect((int32)drag->GetCurrentMousePos().x, 
						(int32)drag->GetCurrentMousePos().y,1,1))) 
		{

			error_log("Mouse is NOT within view area!");
			//drag->ChangeViews(view,old);
			return target->SetPosFromDrag(drag);
		}
		error_log("Mouse IS within view area!");

		drag->ChangeViews(old,view);
		
		return Widget::HandleDragStop(drag);
	}

	bool ScrollAreaView::HandleDragMove( Drag* drag )
	{
		if(!drag)
			return false;

		if(drag->GetStatus() != Drag::Running) 
			return false;

		Widget* target = drag->GetTarget();

		sf::View old = s_gui->GetWindow().GetView();
		float width  = old.GetHalfSize().x*2;
		float height = old.GetHalfSize().y*2;
		
		//apply view transformations to the current drag?
		sf::View view(sf::FloatRect((float)m_scrollArea.x, (float)m_scrollArea.y,
									(float)m_scrollArea.x+width,m_scrollArea.y+height));
		
		//if the mouse is NOT within the view area, don't apply view transformations
		if(!gui::IsCollision(m_rect,Rect((int32)drag->GetCurrentMousePos().x, 
							(int32)drag->GetCurrentMousePos().y,1,1))) 
		{
			error_log("Mouse is NOT within view area!");
			//drag->ChangeViews(view,old);
			return target->SetPosFromDrag(drag);
		}
		error_log("Mouse IS within view area!");

		drag->ChangeViews(old,view);
		
		//basic widget specific child-movement policy, 
		//other widget might move child widgets differently
		return target->SetPosFromDrag(drag);
	}

	bool ScrollAreaView::IsCollision( const Rect& second ) const
	{
		if(!m_editMode) {
			//normally you don't care about events inside the grid
			//only if inside a child of the grids..unless in edit mode
			for(WidgetList::reverse_iterator it = m_widgets.rbegin();
				it != m_widgets.rend(); it++) 
			{
				if(it->second->IsCollision(second)) {
					return true;
				}
			}
			return false;
		} else {
			return Widget::IsCollision(second);
		}
	}

	void ScrollAreaView::BasicDraw() const
	{
		if(!m_visible)
			return;

		Widget::BasicDraw();
// 
// 		sf::Shape shape = sf::Shape::Rectangle(0,0, m_clipRect.w, m_clipRect.h,sf::Color(255,0,255,128));
// 		shape.Move(m_clipRect.x, m_clipRect.y);
// 		sf::String name;
// 		name.SetText(m_name);
// 		name.SetPosition(m_clipRect.x+1, m_clipRect.y+2);
// 		s_gui->GetWindow().Draw(shape);
// 		s_gui->GetWindow().Draw(name);
	}

	bool ScrollArea::IsCollision( const Rect& second ) const
	{
		if(!m_editMode) {
			//normally you don't care about events inside the grid
			//only if inside a child of the grids..unless in edit mode
			for(WidgetList::reverse_iterator it = m_widgets.rbegin();
				it != m_widgets.rend(); it++) 
			{
				if(it->second->IsCollision(second)) {
					return true;
				}
			}
			return false;
		} else {
			return Widget::IsCollision(second);
		}
	}

	bool ScrollArea::RemoveWidget( Widget* widget )
	{
		return m_view->RemoveWidget(widget);
	}

}